/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ru.apertum.diasled.rxtx;

import gnu.io.CommPortIdentifier;
import gnu.io.SerialPortEvent;
import java.io.File;
import java.io.FilenameFilter;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import ru.apertum.diasled.common.Params;
import ru.apertum.diasled.common.Uses;
import ru.apertum.diasled.cfg.Configuration;
import ru.evgenic.rxtx.serialPort.IReceiveListener;
import ru.evgenic.rxtx.serialPort.IReceiveStreamListener;
import ru.evgenic.rxtx.serialPort.ISerialExceptionListener;
import ru.evgenic.rxtx.serialPort.ISerialLoggerListener;
import ru.evgenic.rxtx.serialPort.ISerialPort;
import ru.evgenic.rxtx.serialPort.RxtxSerialPort;

/**
 *
 * @author Evgeniy Egorov
 */
public class PortUtil {

    final static ISerialLoggerListener logger = new ISerialLoggerListener() {

        @Override
        public void actionPerformed(String string, boolean bln) {
            if (bln) {
                Uses.log.logger.debug("COM-порт: " + string);
            }
        }
    };
    final static ISerialExceptionListener exeptHandler = new ISerialExceptionListener() {

        @Override
        public void actionPerformed(String string) throws Exception {
            Uses.log.logger.error("Ошибка COM-портa: " + string);
        }
    };

    static public ArrayList<String> findPorts() {
        final Enumeration portList = CommPortIdentifier.getPortIdentifiers();
        final ArrayList<String> li = new ArrayList<>();
        while (portList.hasMoreElements()) {
            final CommPortIdentifier portIdTemp = (CommPortIdentifier) portList.nextElement();
            if (portIdTemp.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                li.add(portIdTemp.getName());
            }
        }
        return li;
    }

    static public String findDevice() {
        final Enumeration portList = CommPortIdentifier.getPortIdentifiers();
        final ArrayList<String> li = new ArrayList<>();
        final byte[] b = new byte[1];
        b[0] = (byte) 0xf0;
        while (portList.hasMoreElements()) {
            final CommPortIdentifier portIdTemp = (CommPortIdentifier) portList.nextElement();
            if (portIdTemp.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                try {
                    final byte[] res = sendDataInPort(portIdTemp.getName(), b);
                    if (res != null && (res[0] & 255) == 239 && (res[1] & 255) == 165) {// Просто константы
                        return portIdTemp.getName();
                    }
                } catch (Exception ex) {
                    Uses.log.logger.error("В порт не отослалось при определении устройства на порту " + portIdTemp.getName(), ex);
                }
            }
        }
        return null;
    }

    static public byte[] sendDataInPort(String portName, byte[] in) throws Exception {
        final ISerialPort port = new RxtxSerialPort(portName);
        port.setLoggerListener(logger);
        port.setExceptionListener(exeptHandler);
        port.setSpeed(Params.getInstance().getPortSpeed());
        port.setDataBits(Params.getInstance().getPortDatabits());
        port.setParity(Params.getInstance().getPortParity());
        port.setStopBits(Params.getInstance().getPortStopbits());

        final CyclicBarrier cyb = new CyclicBarrier(2);
        final ArrayList<byte[]> res = new ArrayList<>();

        port.bind(new IReceiveListener() {

            @Override
            public void actionPerformed(SerialPortEvent event, byte[] data) {
                res.add(data);
                try {
                    cyb.await(350, TimeUnit.MILLISECONDS);
                } catch (InterruptedException | BrokenBarrierException | TimeoutException ex) {
                }
            }

            @Override
            public void actionPerformed(SerialPortEvent event) {
            }
        });
        port.send(in);

        try {
            cyb.await(350, TimeUnit.MILLISECONDS);
        } catch (InterruptedException | BrokenBarrierException | TimeoutException ex) {
            return null;
        } finally {
            port.free();
        }
        return res.get(0);
    }
    static private Configuration cfg = null;
    static short numCfg = 0;
    static private String portName = null;

    /**
     * Если обломится с железкой, то вернет null.
     * @return конфигурация для подключенной железки
     */
    static public Configuration getConfiguration() {
        // с начала посмотрим на наличие устройства
        portName = findDevice();
        if (portName == null) {
            cfg = null;
            return null;
        }
        // спросим номер устройства
        byte[] request = new byte[1];
        request[0] = (byte) 0x10;
        final byte[] res;
        try {
            res = PortUtil.sendDataInPort(portName, request);
        } catch (Exception ex) {
            return null;
        }
        final ByteBuffer bb = ByteBuffer.allocate(2);
        bb.order(ByteOrder.BIG_ENDIAN);
        bb.put(res[1]);
        bb.put(res[0]);
        final short shortVal = bb.getShort(0);
        // Если такое устройство уже давно, то вернем ранее загруженную конфигурацию
        if (shortVal == numCfg && cfg != null) {
            return cfg;
        }
        // Загрузим конфигурацию
        return Configuration.loadByID(shortVal);
    }

    /**
     * Тут кривовато. Перед подготовкой не забывать получать конфигурацию через getConfiguration()
     * Иначе может порт устройства не определится. Зачем так криво? Да чет подустал и не хотелось бы каждый раз конфиг грузить,
     * а загрузку конфига и подготовку девайса хотелось разделить. 
     * И так сойдет все ровно друг за другом вызываться будут с проверкой конфига на null.
     * @param cfg эту конфу загоним в железку
     * @return имя порта, который подготовили, null если подготовка зафейлилась
     */
    static public String prepareDevice(Configuration cfg) {

        final ISerialPort port;
        try {
            port = new RxtxSerialPort(portName);
        } catch (Exception ex) {
            Uses.log.logger.error("Порт не создался " + portName, ex);
            return null;
        }
        port.setLoggerListener(logger);
        port.setExceptionListener(exeptHandler);
        port.setSpeed(Params.getInstance().getPortSpeed());
        port.setDataBits(Params.getInstance().getPortDatabits());
        port.setParity(Params.getInstance().getPortParity());
        port.setStopBits(Params.getInstance().getPortStopbits());

        byte[] request = new byte[5];
        request[0] = (byte) 0x08;
        request[1] = (byte) 0x00;
        request[2] = (byte) 0x00;
        request[3] = (byte) 0x00;
        request[4] = (byte) 0x00;
        try {
            port.send(request);
            Thread.sleep(150);
        } catch (Exception ex) {
            Uses.log.logger.error("В порт не отослалось", ex);
            return null;
        }

        // Отправка коэф. смещения и усиления
        final ByteBuffer buffer = ByteBuffer.allocate(512 * 2);
        final byte[] ret = new byte[512 * 2];
        int i = 0;
        for (short sh : cfg.getCoefs()) {
            buffer.putShort(sh);
            ret[i * 2] = (byte) (sh & 0xff);
            ret[i * 2 + 1] = (byte) ((sh >> 8) & 0xff);
            i++;
        }
        request[0] = (byte) 0x03;
        try {
            port.send(request);
            Thread.sleep(150);
            port.send(ret);
            Thread.sleep(150);
        } catch (Exception ex) {
            Uses.log.logger.error("В порт не отослалось", ex);
            return null;
        }
        return portName;
    }

    static public InputStream startExam(String portNm, int frames) {
        try {
            portExam = new RxtxSerialPort(portNm);
        } catch (Exception ex) {
            Uses.log.logger.error("Порт не создался " + portNm, ex);
            return null;
        }
        portExam.setLoggerListener(logger);
        portExam.setExceptionListener(exeptHandler);
        portExam.setSpeed(Params.getInstance().getPortSpeed());
        portExam.setDataBits(Params.getInstance().getPortDatabits());
        portExam.setParity(Params.getInstance().getPortParity());
        portExam.setStopBits(Params.getInstance().getPortStopbits());

        byte[] request = new byte[6];
        request[0] = (byte) 0x04;
        /*
        #define SLED_EXPERIMENT_DATA    0x01
        #define SLED_RAW_DATA           0x02
        #define SLED_DEBUG_DATA         0x03
         */
        request[1] = (byte) 0x01;
        request[5] = (byte) (frames >>> 24);
        request[4] = (byte) (frames >>> 16);
        request[3] = (byte) (frames >>> 8);
        request[2] = (byte) frames;

        final CyclicBarrier cb = new CyclicBarrier(2);
        final ArrayList<InputStream> exchEx = new ArrayList<>();
        try {
            portExam.bind(new IReceiveStreamListener() {

                @Override
                public void actionPerformed(SerialPortEvent event, InputStream in) {
                    if (exchEx.isEmpty()) {
                        exchEx.add(in);
                        try {
                            cb.await();
                        } catch (InterruptedException | BrokenBarrierException ex) {
                            Uses.log.logger.error("Непонятная ошибка 1. ", ex);
                        }
                    }
                }

                @Override
                public void actionPerformed(SerialPortEvent event) {
                }
            });
        } catch (Exception ex) {
            return null;
        }
        try {
            portExam.send(request);
        } catch (Exception ex) {
            return null;
        }

        try {
            cb.await(1000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException | BrokenBarrierException | TimeoutException ex) {
            return null;
        }
        if (exchEx.isEmpty()) {
            return null;
        }
        return exchEx.get(0);
    }
    private static ISerialPort portExam;

    static public void stopExam() {
        final byte[] bb = new byte[1];
        bb[0] = (byte) 0x05;
        try {
            portExam.send(bb);
            Thread.sleep(150);
        } catch (Exception ex) {
            Uses.log.logger.error("Ошибка при отправки останавливающей команды в порт. ", ex);
        }
        closePortExam();
    }

    static public void closePortExam() {
        if (portExam != null) {
            try {
                portExam.free();
            } catch (Exception ex) {
                Uses.log.logger.error("Ошибка при закрытии порта эксперимента. ", ex);
            }
            portExam = null;
        }
    }
}
